﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Windows;
using Common.Utility;
using OxyPlot;

namespace AutoCorrelatorGUI.Model
{
   public class DataService : IDataService
   {
      public event Action<string> StatusMessage;
      public event Action<DataUpdateTypes> DataUpdated;

      private Dictionary<string, Well> _TitleToWell = null;
      private BindingList<Well> _WellData = null;
      private bool _IsRefreshing = false;

      //Options
      private NormalizationTypes _Normalization = NormalizationTypes.None;
      private bool _CalcDerivative = false;
      private string _ComparisonWellTitle = "";
      private double _IntervalTimeMS = 2.0;
      private MotherWaveletTypes _MotherWavelet = MotherWaveletTypes.Haar;

      public void GetAllWells(Action<IList<Well>> callback)
      {
         //Ignore if this is called before we load the data
         if (_WellData == null)
            return;

         try
         {
            callback(_WellData);
         }
         catch (Exception ex)
         {
            OnStatusMessage(ex.Message);
         }
      }

      public void SetInputFile(string inputFile, bool performAutoCorrelation)
      {
         try
         {
            if (!File.Exists(inputFile))
            {
               throw new FileNotFoundException("Could not find the input file specified", inputFile);
            }

            //Load file
            string[] lines = File.ReadAllLines(inputFile);

            if (lines.Length == 0)
            {
               throw new FileLoadException("File did not contain any data", inputFile);
            }

            //First line is the object names
            string[] names = lines[0].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            if (names.Length == 0)
            {
               throw new FileLoadException("File did not any name data", inputFile);
            }

            List<Well> wells = new List<Well>();
            List<List<double>> tempWellData = new List<List<double>>();

            foreach (string name in names)
            {
               tempWellData.Add(new List<double>());
            }

            for (int i = 1; i < lines.Length; i++)
            {
               //Split the line into data
               string[] dataLine = lines[i].Split(','); //Do not remove empty entries

               for (int j = 0; j < dataLine.Length; j++)
               {
                  if (String.IsNullOrWhiteSpace(dataLine[j]))
                     continue;

                  double parsedValue = 0.0;

                  if (Double.TryParse(dataLine[j], out parsedValue))
                  {
                     tempWellData[j].Add(parsedValue);
                  }
                  else
                  {
                     throw new FormatException("Error converting: " + dataLine[j] + ", to a number. Ensure file contains only title names and numbers.");
                  }
               }
            }

            if (_TitleToWell == null)
               _TitleToWell = new Dictionary<string, Well>();

            _TitleToWell.Clear();

            for (int i = 0; i < names.Length; i++)
            {
               OxyColor indexColor = DefaultPalettes.Color[i % DefaultPalettes.Color.Count];

               var well = new Well(names[i]);

               well.Input.ProcessRawData(tempWellData[i]);

               wells.Add(well);
               _TitleToWell[well.Title] = well;
            }

            _WellData = new BindingList<Well>(wells);

            OnFullRefresh();
         }
         catch (Exception ex)
         {
            OnStatusMessage(ex.Message);
         }
      }

      private void OnStatusMessage(string statusMessage)
      {
         if (StatusMessage != null)
         {
            StatusMessage(statusMessage);
         }
      }

      public void SetNormalization(NormalizationTypes normType)
      {
         if (_Normalization == normType)
            return;

         _Normalization = normType;

         //Check to make sure we have loaded some data before 
         if (_WellData == null)
            return;

         UpdateOutput();
      }

      public void SetCalcDerivative(bool calcDerivative)
      {
         if (_CalcDerivative == calcDerivative)
            return;

         _CalcDerivative = calcDerivative;

         //Check to make sure we have loaded some data before 
         if (_WellData == null)
            return;

         UpdateOutput();
      }

      public void SetCompareWell(string compareTitle)
      {
         if (_ComparisonWellTitle == compareTitle)
            return;

         _ComparisonWellTitle = compareTitle;

         //Check to make sure we have loaded some data before 
         if (_WellData == null)
            return;

         UpdateComparison();
      }

      public void SetIntervalTime(double intervalMS)
      {
         if (_IntervalTimeMS == intervalMS)
            return;

         _IntervalTimeMS = intervalMS;

         //Check to make sure we have loaded some data before 
         if (_WellData == null)
            return;

         UpdateAnalysis();
      }

      public void SetMotherWavelet(MotherWaveletTypes waveType)
      {
         if (_MotherWavelet == waveType)
            return;

         _MotherWavelet = waveType;

         //Check to make sure we have loaded some data before 
         if (_WellData == null)
            return;

         UpdateTransform();
      }

      private void OnFullRefresh()
      {
         _IsRefreshing = true;

         //Indicate the input changed for trickle down calculations
         OnInputChanged();

         if (DataUpdated != null)
            DataUpdated(DataUpdateTypes.Full);

         _IsRefreshing = false;
      }

      private void OnInputChanged()
      {
         UpdateOutput();

         if (!_IsRefreshing && DataUpdated != null)
            DataUpdated(DataUpdateTypes.Input);
      }

      private void UpdateOutput()
      {
         for (int i = 0; i < _WellData.Count; i++)
         {
            _WellData[i].Output.ProcessInput(_WellData[i].Input, _Normalization, _CalcDerivative);
         }

         //Indicate the input changed for trickle down calculations
         UpdateAnalysis();
         UpdateTransform();

         if (!_IsRefreshing && DataUpdated != null)
            DataUpdated(DataUpdateTypes.Output);
      }

      private void UpdateAnalysis()
      {
         for (int i = 0; i < _WellData.Count; i++)
         {
            _WellData[i].Analysis.Calc(_WellData[i].Output, _IntervalTimeMS);
         }

         if (!_IsRefreshing && DataUpdated != null)
            DataUpdated(DataUpdateTypes.Analysis);
      }

      private void UpdateComparison()
      {
         if (!_TitleToWell.ContainsKey(_ComparisonWellTitle))
            return;

         for (int i = 0; i < _WellData.Count; i++)
         {
            _WellData[i].Comparison.Calc(_WellData[i], _TitleToWell[_ComparisonWellTitle]);
         }

         if (!_IsRefreshing && DataUpdated != null)
            DataUpdated(DataUpdateTypes.Comparison);
      }

      private void UpdateTransform()
      {
         uint maxCount = 0;

         for (int i = 0; i < _WellData.Count; i++)
         {
            maxCount = Math.Max(maxCount, (uint)_WellData[i].Output.Data.Count);
         }

         for (int i = 0; i < _WellData.Count; i++)
         {
            _WellData[i].Transform.Calc(_WellData[i].Output, _IntervalTimeMS, _MotherWavelet, maxCount);
         }

         UpdateComparison();

         if (!_IsRefreshing && DataUpdated != null)
            DataUpdated(DataUpdateTypes.Transform);
      }

      public void SaveOutputData(string outputFile)
      {
         throw new NotImplementedException();
      }
   }
}
